from datetime import datetime, timedelta
import backtrader as bt
from backtrader.utils import logger
import collections
import numpy as np
import json
import os


class BaseStrategy(bt.Strategy):
    params = (
        # period for the fast Moving Average
        ('name', "BaseStrategy"),
        ('token', ""),
        ("leverage", 1),
        ('enable', False),
    )

    def __init__(self):
        super(BaseStrategy, self).__init__()
        self.run_count = 0
        self.symbol = self.data._dataname
        self.markets = self.broker.fetch_market(symbol=self.symbol)
        self.logger = logger.getLogger(__name__)
        self.alert = bt.utils.Alert(self.p.token, self.p.enable)
        self.dump_cfg = bt.AutoOrderedDict()

        self.cfg_dir = os.path.join(os.path.expanduser('~'), ".mercury")
        if not os.path.exists(self.cfg_dir):
            os.mkdir(self.cfg_dir)

        self.load()

    def send_msg(self, action, content, dt):
        _format = self.alert.format()
        params = {
            "name": self.p.name,
            "asset": self.symbol,
            "action": action,
            "detail": content,
            "date": dt
        }
        self.alert.send(_format.format(**params))

    def cal_swap_size(self, money, price, leverage):
        """
        把钱换算成多少张合约
        """
        # 合约面值
        ctVal = float(self.markets["info"]["ctVal"])
        return int((money * leverage) / (ctVal * price))

    def cal_swap_value(self, price, leverage, size):
        """
        买一张合约的价格
        """
        ctVal = float(self.markets["info"]["ctVal"])
        value = size * ctVal * price
        margin = value / leverage
        return value, margin

    """
    买现货, 现货market买入的size是买多少钱
    """
    def buy_spot(self, exectype, size, price):
        if exectype == bt.Order.Market:
            if price is None:
                raise Exception(f"market must give the price")
        return self.buy(exectype=exectype, size=size, price=price)

    """
    卖现货
    """
    def sell_spot(self,  exectype, size, price=None):
        if exectype != bt.Order.Market and price is None:
            raise Exception("exec type is not market, please give the price.")
        return self.sell(exectype=exectype, size=size, price=price)

    """
    合约是多少张, 每张只多少个交易货币是从fetch_market返回的
    比特币一张合约值0.01 btc, 以太坊一张合约值0.1 eth
    """
    def open_long(self, exectype, size, price):
        tdMode, posSide, sz = "isolated", "long", int(size)
        return self.buy(exectype=exectype, size=size, price=price, tdMode=tdMode, posSide=posSide, sz=sz)

    """
    设置止盈止损的时候，可平仓量变少，不能强平，必须先取消止盈止损, 或者直接使用市价强平
    """
    def close_long(self, exectype, size, price):
        tdMode, posSide, sz = "isolated", "long", int(size)
        return self.sell(exectype=exectype, size=size, price=price, tdMode=tdMode, posSide=posSide, sz=sz)

    def open_short(self, exectype, size, price):
        tdMode, posSide, sz = "isolated", "short", int(size)
        return self.sell(exectype=exectype, size=size, price=price, tdMode=tdMode, posSide=posSide, sz=sz)

    def close_short(self, exectype, size, price):
        tdMode, posSide, sz = "isolated", "short", int(size)
        return self.buy(exectype=exectype, size=size, price=price, tdMode=tdMode, posSide=posSide, sz=sz)

    """
    止损单
    """
    def stop_loss_swap(self, symbol, size, side, posSide, slTriggerPx, slOrdPx):
        """
        private_post_trade_order_algo: 策略委托，止盈止损
        private_post_trade_cancel_algos: 取消策略委托
        private_post_trade_close_position: 按市价平仓
        """
        exchange = self.broker.store.exchange
        params = {
            "instId": symbol,
            "ordType": "conditional",  # conditional：单向止盈止损, oco：双向止盈止损, trigger：计划委托
            "tdMode": "isolated",       # 逐仓
            "posSide": posSide,         # 双边持仓指定long还是short
            "side": side,                # 操作方向是买还是卖
            "sz": size,
            "slTriggerPx": slTriggerPx,  # 止损触发价
            "slOrdPx": slOrdPx  # 止损委托价, -1就是市价委托
        }
        _order = exchange.private_post_trade_order_algo(params=params)
        if _order["code"] == '0':
            self.log('**NOTIFY** ORDER Stop Loss Swap, Side: %s, posSide: %s, Trigger price: %.2f, Order price: %.2f, Size %.2f' %
                     (side, posSide, slTriggerPx, slOrdPx, size))
            return _order["data"][0]["algoId"]
        else:
            return None


    """
    现货止损单
    """
    def stop_loss_spot(self, symbol, size, slTriggerPx, slOrdPx):
        """
        private_post_trade_order_algo: 策略委托，止盈止损
        private_post_trade_cancel_algos: 取消策略委托
        private_post_trade_close_position: 按市价平仓
        """
        exchange = self.broker.store.exchange
        name = symbol.replace("/", "-")
        params = {
            "instId": name,
            "ordType": "conditional",  # conditional：单向止盈止损, oco：双向止盈止损, trigger：计划委托
            "tdMode": "cash",  # 逐仓
            "side": "sell",  # 操作方向是买还是卖
            "sz": size,
            "slTriggerPx": slTriggerPx,  # 止损触发价
            "slOrdPx": slOrdPx  # 止损委托价, -1就是市价委托
        }
        _order = exchange.private_post_trade_order_algo(params=params)
        if _order["code"] == '0':
            self.log('**NOTIFY** ORDER Stop Loss Spot, Trigger price: %.2f, Order price: %.2f, Size %.2f' %
                    (slTriggerPx, slOrdPx, size))
            return _order["data"][0]["algoId"]
        else:
            return None

    def fetch_open_stop_loss_orders(self, symbol, ordType="conditional"):
        """
        {'code': '0', 'data': [{'actualPx': '', 'actualSide': '', 'actualSz': '0', 'algoId': '338042238030725120',
        'cTime': '1626867550067', 'ccy': '', 'instId': 'ADA-USDT', 'instType': 'SPOT', 'lever': '',
        'ordId': '0', 'ordPx': '', 'ordType': 'conditional', 'posSide': 'net', 'side': 'sell',
        'slOrdPx': '1.12', 'slTriggerPx': '1.12', 'state': 'live', 'sz': '49', 'tdMode': 'cash',
        'tpOrdPx': '', 'tpTriggerPx': '', 'triggerPx': '', 'triggerTime': ''}], 'msg': ''}

        """
        name = symbol.replace("/", "-")
        params = {
            "instId": name,
            "ordType": ordType  # conditional：单向止盈止损, oco：双向止盈止损, trigger：计划委托
        }
        exchange = self.broker.store.exchange
        _order = exchange.private_get_trade_orders_algo_pending(params=params)
        if _order["code"] == '0':
            return _order["data"]
        else:
            return []

    def cancel_stop_loss(self, symbol, algoId):
        exchange = self.broker.store.exchange
        name = symbol.replace("/", "-")
        params = [
            {
                "instId": name,
                "algoId": algoId
            }
        ]
        return exchange.private_post_trade_cancel_algos(params=params)

    """
    设置止盈止损的时候，无论是双向还是单向， 市价全平是起作用的
    按照市价全平
    """
    def close_market(self, symbol, posSide):
        exchange = self.broker.store.exchange
        params = {
            "instId": symbol,
            "posSide": posSide,
            "mgnMode": "isolated"
        }
        return exchange.private_post_trade_close_position(params=params)

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        if dt is None:
            dt = datetime.now().timestamp() * 1000
        dt_str = datetime.fromtimestamp(dt / 1000).strftime("%Y-%m-%d %H:%M:%S")
        self.logger.info("%s, %s", dt_str, txt)

    def prenext(self):
        '''
        从第一条数据开始, 在miniperiod 阶段中调用, 当满足miniperiod时候就不调用了
        比如ma(3), 前两条调用prenext, 第3条数据的时候调用next
        '''
        self.run_count += 1
        for i, d in enumerate(self.datas):
            if len(d) > 0:
                dtstr = d.datetime.datetime() + timedelta(hours=8)
                self.logger.info("prenext[%d]: [%s]-[%s]-%s", i, d._name, self.run_count, dtstr)

    def notify_data(self, data, status, *args, **kwargs):
        dn = data._name
        dt = datetime.now()
        msg = 'Data Status: {}'.format(data._getstatusname(status))
        self.logger.info("Notify Data: %s, %s, %s", dt, dn, msg)

        if data._getstatusname(status) == 'LIVE':
            self.live_data = True
        else:
            self.live_data = False

    def notify_order(self, order):
        if order.status == order.Submitted:
            self.log('**NOTIFY** ORDER SUBMITTED', dt=order.created.dt)
            self.order = order
            return
        if order.status == order.Accepted:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            self.log('**NOTIFY** ORDER ACCEPTED', dt=order.created.dt)
            self.order = order
            return

        if order.status in [order.Expired]:
            self.log('**NOTIFY** ORDER BUY EXPIRED', dt=order.created.dt)

        elif order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    '**NOTIFY** ORDER BUY COMPLETED, Price: %.2f, Size: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.size,
                     order.executed.comm), dt=order.executed.dt)

            else:  # Sell
                self.log('**NOTIFY** ORDER SELL COMPLETED, Price: %.2f, Size: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.size,
                          order.executed.comm), dt=order.executed.dt)
        elif order.status in [order.Margin]:
            self.log(
                '**NOTIFY** ORDER Failed, not cash', dt=order.executed.dt
            )

        # Sentinel to None: new orders allowed
        self.order = None

    def dump_status(self):
        pass

    def dump(self):
        self.dump_status()
        cfg_path = os.path.join(self.cfg_dir, f"{self.p.name}.json")

        with open(cfg_path, 'w') as f:
            json.dump(self.dump_cfg, f, indent=4)

    def load(self):
        cfg_path = os.path.join(self.cfg_dir, f"{self.p.name}.json")
        if os.path.exists(cfg_path):
            with open(cfg_path, 'r') as f:
                data = json.load(f)
            for k, v in data.items():
                self.dump_cfg[k] = v